/*
 * SubSonic - http://subsonicproject.com
 * 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an 
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;

namespace SubSonic
{
    /// <summary>
    /// Summary for the ActiveController&lt;ItemType, ListType&gt; class
    /// </summary>
    /// <typeparam name="ItemType">The type of the tem type.</typeparam>
    /// <typeparam name="ListType">The type of the ist type.</typeparam>
    public abstract class ActiveController<ItemType, ListType> : MarshalByRefObject
        where ItemType : AbstractRecord<ItemType>, new()
        where ListType : List<ItemType>, new()
    {
        #region "Constructor"

        private static object _lockController = null;
        protected static object LockController
        {
            get { return _lockController; }
        }

        static ActiveController()
        {
            _lockController = new object();
        }

        public ActiveController()
            : base()
        {
        }

        #endregion

        #region "Validators"

        public bool CheckExists(object key)
        {
            ItemType item = this.Get(key);
            return (item.IsLoaded);
        }

        public bool CheckUniqueColumn(string column, object value)
        {
            return this.CheckUniqueColumn(column, value, null);
        }

        public bool CheckUniqueColumn(string column, object value, object key)
        {
            ItemType item = null;

            if (key == null)
            {
                item = this.Get(
                     ActiveController<ItemType, ListType>.QueryAll()
                         .AddWhere(column, value)
                     );
            }
            else
            {
                item = this.Get(
                    ActiveController<ItemType, ListType>.QueryAll()
                         .AddWhere(column, value)
                         .AddWhere(item.GetSchema().PrimaryKey.ColumnName, Comparison.NotEquals, key)
                     );
            }
            return (item.IsNew);
        }

        #endregion

        /// <summary>
        /// Lists this instance.
        /// </summary>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, true)]
        public ListType List()
        {
            Query q = Query();
            return List(q);
        }

        /// <summary>
        /// Lists this instance in a specified order
        /// </summary>
        /// <param name="orderBy">orderBy</param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType List(OrderBy orderBy)
        {
            orderBy.TrimDirective();
            Query qry = Query().ORDER_BY(orderBy.OrderString);

            return this.List(qry);
        }

        /// <summary>
        /// Lists the specified q.
        /// </summary>
        /// <param name="q">The q.</param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType List(Query q)
        {
            ListType list = new ListType();
            IDataReader rdr = q.ExecuteReader();
            while(rdr.Read())
            {
                ItemType item = new ItemType();
                item.Load(rdr);
                list.Add(item);
            }
            rdr.Close();
            return list;
        }

        /// <summary>
        /// Lists the specified RDR.
        /// </summary>
        /// <param name="rdr">The RDR.</param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType List(IDataReader rdr)
        {
            ListType list = new ListType();
            while(rdr.Read())
            {
                ItemType item = new ItemType();
                item.Load(rdr);
                list.Add(item);
            }
            rdr.Close();
            return list;
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType List(StoredProcedure storedProc)
        {
            return this.List(storedProc.GetReader());
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType List(string columnName, object param)
        {
            return this.List(Query().AddWhere(columnName, param));
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType List(string columnName, object param, OrderBy orderBy)
        {
            orderBy.TrimDirective();
            Query qry = Query().AddWhere(columnName, param).ORDER_BY(orderBy.OrderString);

            return this.List(qry);
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType ListAll(string columnName, object param)
        {
            return this.List(QueryAll().AddWhere(columnName, param));
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType ListAll(string columnName, object param, OrderBy orderBy)
        {
            orderBy.TrimDirective();
            Query qry = QueryAll().AddWhere(columnName, param).ORDER_BY(orderBy.OrderString);

            return this.List(qry);
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType ListAll()
        {
            return this.List(QueryAll());
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ListType ListAll(OrderBy orderBy)
        {
            orderBy.TrimDirective();
            Query qry = QueryAll().ORDER_BY(orderBy.OrderString);
            return this.List(qry);
        }

        /// <summary>
        /// Creates a Query for this instance.
        /// </summary>
        /// <returns></returns>        
        public static Query Query()
        {
            ItemType item = new ItemType();
            Query q = new Query(item.GetSchema());
            q.CheckLogicalDelete();
            return q;
        }

        /// <summary>
        /// Creates a Query for this instance.
        /// </summary>
        /// <returns></returns>    
        public static Query QueryAll()
        {
            ItemType item = new ItemType();            
            Query q = new Query(item.GetSchema());

            return q;
        }


        /// <summary>
        /// Gets the specified key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ItemType Get(object key)
        {
            ItemType item = new ItemType();
            TableSchema.Table schema = item.GetSchema();

            return this.Get(schema.PrimaryKey.ColumnName, key);
        }

        /// <summary>
        /// Gets the specified column name.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <param name="param">The param.</param>
        /// <returns></returns>
        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ItemType Get(string columnName, object param)
        {            
            return this.Get(QueryAll().WHERE(columnName, param));
        }

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public ItemType Get(Query q)
        {
            ItemType item = new ItemType();
            item.LoadAndCloseReader(q.ExecuteReader());
            return item;
        }

        //Insert
        //Update

        /// <summary>
        /// Saves this object's state to the selected Database.
        /// </summary>
        /// <param name="userName"></param>
        public void Save(ItemType item, string userName)
        {
            CancelEventArgs cancelArgs = new CancelEventArgs();
            bool inserting = true;

            item.BeforeValidate(cancelArgs);
            if (cancelArgs.Cancel)
                return;

            bool isValid = item.Validate();
            if (isValid && item.IsDirty)
            {
                item.AfterValidate();

                cancelArgs = new CancelEventArgs();
                if (item.IsNew)
                    item.BeforeInsert(cancelArgs);
                else if (item.IsDirty)
                {
                    inserting = false;
                    item.BeforeUpdate(cancelArgs);
                }

                if (cancelArgs.Cancel)
                    return;

                TableSchema.Table schema = item.GetSchema();
                QueryCommand cmd = item.IsNew ? GetInsertCommand(item, userName) : GetUpdateCommand(item, userName);

                if (cmd == null)
                    return;

                //reset the Primary Key with the id passed back by the operation
                object pkVal = DataService.ExecuteScalar(cmd);

                if (pkVal != null)
                {
                    if (pkVal.GetType() == typeof(decimal))
                        pkVal = Convert.ToInt32(pkVal);
                    //set the primaryKey, only if an auto-increment
                    //if (table.PrimaryKey.AutoIncrement)
                    if (schema.PrimaryKey.AutoIncrement || schema.PrimaryKey.DataType == DbType.Guid)
                    {
                        try
                        {
                            item.SetColumnValue(schema.PrimaryKey.ColumnName, pkVal);
                        }
                        catch
                        {
                            //this will happen if there is no PK defined on a table. Catch this and notify
                            throw new Exception("No Primary Key is defined for this table. A primary key is required to use SubSonic");
                        }
                    }
                }
                //set this object as old
                item.MarkOld();
                item.MarkClean();

                if (inserting)
                    item.AfterInsert();
                else
                    item.AfterUpdate();

                item.AfterCommit();
            }
            else
            {
                //throw an Exception
                string notification = string.Empty;
                foreach (string message in item.Errors)
                {
                    notification += message + Environment.NewLine;
                }
                if (!String.IsNullOrEmpty(notification))
                {
                    throw new Exception("Can't save: " + notification);
                }
            }
        }

        /// <summary>
        /// Made Public for use with transactions
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public QueryCommand GetInsertCommand(ItemType item, string userName)
        {
            return ActiveHelper<ItemType>.GetInsertCommand(item, userName);
        }

        /// <summary>
        /// Gets the update command.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="userName">Name of the user.</param>
        /// <returns></returns>
        public QueryCommand GetUpdateCommand(ItemType item, string userName)
        {
            return ActiveHelper<ItemType>.GetUpdateCommand(item, userName);
        }

        /// <summary>
        /// Gets the appropriate Insert or Update command for this item
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="userName">Name of the user.</param>
        /// <returns></returns>
        public QueryCommand GetSaveCommand(ItemType item, string userName)
        {
            if (item.IsNew)
                return this.GetInsertCommand(item, userName);
            return this.GetUpdateCommand(item, userName);
        }

        /// <summary>
        /// Deletes the specified key ID.
        /// </summary>
        /// <param name="keyID">The key ID.</param>
        [DataObjectMethod(DataObjectMethodType.Delete, false)]
        public void Delete(ItemType item, string userName)
        {
            this.Delete(
                item, 
                item.GetSchema().PrimaryKey.ColumnName,
                item.GetPrimaryKeyValue(),
                userName
            );            
        }

        /// <summary>
        /// Deletes the specified column name.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <param name="param">The param.</param>
        [DataObjectMethod(DataObjectMethodType.Delete, false)]
        public void Delete(ItemType item, string columnName, object param, string userName)
        {
            CancelEventArgs cancelArgs = new CancelEventArgs();
            item.BeforeDelete(cancelArgs);
            if (!cancelArgs.Cancel)
            {                
                try
                {
                    ActiveHelper<ItemType>.Delete(
                        columnName,
                        param,
                        userName);
                    item.AfterDelete();
                }
                catch (Exception ex)
                {
                    // rethrow exception from DB provider
                    throw ex;
                }
            }
        }

        /// <summary>
        /// Destroys the specified key ID.
        /// </summary>
        /// <param name="keyID">The key ID.</param>
        [DataObjectMethod(DataObjectMethodType.Delete, false)]
        public void Destroy(ItemType item, string userName)
        {
            this.Destroy(
                item,
                item.GetSchema().PrimaryKey.ColumnName,
                item.GetPrimaryKeyValue(),
                userName
            );
        }

        /// <summary>
        /// Destroys the specified column name.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <param name="param">The param.</param>
        [DataObjectMethod(DataObjectMethodType.Delete, false)]
        public void Destroy(ItemType item, string columnName, object param, string userName)
        {
            CancelEventArgs cancelArgs = new CancelEventArgs();
            item.BeforeDelete(cancelArgs);
            if (!cancelArgs.Cancel)
            {
                try
                {
                    ActiveHelper<ItemType>.DestroyByParameter(columnName, param);                    
                    item.AfterDestroy();
                }
                catch (Exception ex)
                {
                    // rethrow exception from DB provider
                    throw ex;
                }
            }
        }
        
        /// <summary>
        /// Gets the delete command.
        /// </summary>
        /// <param name="keyID">The key ID.</param>
        /// <returns></returns>
        public QueryCommand GetDeleteCommand(object keyID)
        {
            return ActiveHelper<ItemType>.GetDeleteCommand(keyID);
        }

        /// <summary>
        /// Gets the delete command.
        /// </summary>
        /// <param name="columnName">Name of the column.</param>
        /// <param name="oValue">The o value.</param>
        /// <returns></returns>
        public QueryCommand GetDeleteCommand(string columnName, object oValue)
        {
            return ActiveHelper<ItemType>.GetDeleteCommand(columnName, oValue);
        }

        #region "Remote Stored Procedures"
        public int Execute(StoredProcedure storedProc)
        {
            return storedProc.Execute();
        }

        public object ExecuteScalar(StoredProcedure storedProc)
        {
            return storedProc.ExecuteScalar();
        }

        public DataSet GetDataSet(StoredProcedure storedProc)
        {
            return storedProc.GetDataSet();
        }

        public DataSet GetDataSet(Query query)
        {
            return query.ExecuteDataSet();
        }

        public T GetDataSet<T>(StoredProcedure storedProc)
            where T : DataSet, new()
        {
            return storedProc.GetDataSet<T>();
        }

        public T GetDataSet<T>(Query query)
            where T : DataSet, new()
        {
            return query.ExecuteDataSet<T>();
        }
        #endregion
    }
}
